<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <title>Geodesic lines, circles, envelopes in Google Maps</title>
    <meta name="description"
	  content="Geodesic lines, circles, envelopes in Google Maps" />
    <meta name="author" content="Charles F. F. Karney">
    <meta name="keywords"
	  content="geodesics,
		   geodesic distance,
		   geographic distance,
		   shortest path,
		   direct geodesic problem,
		   inverse geodesic problem,
		   distance and azimuth,
		   distance and heading,
		   range and bearing,
		   geographic circle,
		   geodesic envelope,
		   geodesic astroid,
		   latitude and longitude,
		   Google Maps,
		   WGS84 ellipsoid,
		   GeographicLib" />
    <style>
      html, body, #map-canvas {
        height: 100%;
        margin: 0px;
        padding: 0px
      }
    </style>
    <script type="text/javascript"
	    src="http://geographiclib.sf.net/scripts/geographiclib.js">
    </script>
    <script type="text/javascript"
	    src="https://maps.googleapis.com/maps/api/js?sensor=false">
    </script>
    <script type="text/javascript">
var geod = GeographicLib.Geodesic.WGS84;
var dms = GeographicLib.DMS;
var geodesic, circle, envelope;

function initialize() {
  var mapOptions = {
    zoom: 8,
    center: new google.maps.LatLng(41.3, -5.5),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

  var geodesicOptions = {
    strokeColor: '#0000FF',
    strokeOpacity: 1,
    strokeWeight: 3,
    geodesic: true
  }
  geodesic = new google.maps.Polyline(geodesicOptions);
  geodesic.setMap(map);

  var circleOptions = {
    strokeColor: '#00FF00',
    strokeOpacity: 1,
    strokeWeight: 3,
    geodesic: true
  }
  circle = new google.maps.Polyline(circleOptions);
  circle.setMap(map);

  var envelopeOptions = {
    strokeColor: '#FF0000',
    strokeOpacity: 1,
    strokeWeight: 3,
    geodesic: true
  }
  envelope = new Array(4);
  for (var i = 0; i < 4; ++i) {
    envelope[i] = new google.maps.Polyline(envelopeOptions);
    envelope[i].setMap(map);
  }
}

function formatpoint(lat, lon, azi, dmsformat, prec) {
  prec += 5;
  if (dmsformat) {
    var trail = prec < 2 ? dms.DEGREE :
      (prec < 4 ? dms.MINUTE : dms.SECOND);
    prec = prec < 2 ? prec : (prec < 4 ? prec - 2 : prec - 4);
    return (dms.Encode(lat, trail, prec, dms.LATITUDE) + " " +
	    dms.Encode(lon, trail, prec, dms.LONGITUDE) + " " +
	    dms.Encode(azi, trail, prec, dms.AZIMUTH));
  } else {
    return (lat.toFixed(prec) + " " +
	    lon.toFixed(prec) + " " +
	    azi .toFixed(prec));
  }
}

function GeodesicInverse(input) {
  var result = {};
  try {
    // Input is a blank-delimited line: lat1 lon1 lat2 lon2
    var t = new String(input);
    t = t.replace(/^\s+/,"").replace(/\s+$/,"").split(/[\s,]+/,6);
    if (t.length != 4)
      throw new Error("Need 4 input items");
    var p1 = GeographicLib.DMS.DecodeLatLon(t[0], t[1]);
    var p2 = GeographicLib.DMS.DecodeLatLon(t[2], t[3]);
    var v = geod.Inverse(p1.lat, p1.lon, p2.lat, p2.lon);
    result.status = "OK";
    result.p1 = formatpoint(v.lat1, v.lon1, v.azi1, true, 0);
    result.p2 = formatpoint(v.lat2, v.lon2, v.azi2, true, 0);
    result.s12 = v.s12.toFixed(0);
    draw(v);
  }
  catch (e) {
    result.status = "ERROR: " + e.message;
    result.p1 = "";
    result.p2 = "";
    result.s12 = "";
  }
  return result;
}
function GeodesicDirect(input) {
  var result = {};
  try {
    // Input is a blank-delimited line: lat1 lon1 azi1 s12
    var t = new String(input);
   t = t.replace(/^\s+/,"").replace(/\s+$/,"").split(/[\s,]+/,6);
    if (t.length != 4)
      throw new Error("Need 4 input items");
    var p1 = GeographicLib.DMS.DecodeLatLon(t[0], t[1]);
    var azi1 = GeographicLib.DMS.DecodeAzimuth(t[2]);
    var s12 = parseFloat(t[3]);
    var v =  geod.Direct(p1.lat, p1.lon, azi1, s12);
    result.status = "OK";
    result.p1 = formatpoint(v.lat1, v.lon1, v.azi1, true, 0);
    result.p2 = formatpoint(v.lat2, v.lon2, v.azi2, true, 0);
    result.s12 = v.s12.toFixed(0);
    draw(v);
  }
  catch (e) {
    result.status = "ERROR: " + e.message;
    result.p1 = "";
    result.p2 = "";
    result.s12 = "";
  }
  return result;
}

function draw(v) {
  var points = geod.DirectPath(v.lat1, v.lon1, v.azi1, v.s12,
			100000, 100);
  clearPaths();
  var i, k, path;

  path = geodesic.getPath();
  for (k = 0; k < points.length; ++k)
    path.push(new google.maps.LatLng(points[k].lat, points[k].lon));
  points = geod.Circle(v.lat1, v.lon1, v.azi1, v.s12, 72);
  path = circle.getPath();
  for (k = 0; k < points.length; ++k)
    path.push(new google.maps.LatLng(points[k].lat, points[k].lon));
  for (i = 0; i < 4; ++i) {
    points = geod.Envelope(v.lat1, v.lon1, 72, i+1);
    path = envelope[i].getPath();
    for (k = 0; k < points.length; ++k)
      path.push(new google.maps.LatLng(points[k].lat, points[k].lon));
  }
  map.panTo(new google.maps.LatLng(v.lat2, v.lon2));
}

function clearPaths() {
  cPath = geodesic.getPath();
  while (cPath.getLength()) cPath.pop();
  cPath = circle.getPath();
  while (cPath.getLength()) cPath.pop();
  for (var i = 0; i < 4; ++i) {
    cPath = envelope[i].getPath();
    while (cPath.getLength()) cPath.pop();
  }
}
    </script>
  </head>
  <body onload="initialize()">
    <div id="map-canvas"
	 style="position:relative; border: 1px solid black; width:99.5%;
	 height:72%;">
    </div>
    <div>
      <p>
	&nbsp;Direct:&nbsp;&nbsp;
	<input id="inputb" size=60 value="41°19'S 174°49'E 135° 60000e3" />
	<input type="button" value="compute"
	       onclick="var t = GeodesicDirect(document.getElementById('inputb').value);
			document.getElementById('status').value = t.status;
			document.getElementById('p1').value = t.p1;
			document.getElementById('p2').value = t.p2;
			document.getElementById('s12').value = t.s12;" />
      </p>
      <p>
	&nbsp;Inverse:
	<input id="inputa" size=60 value="41°19'S 174°49'E 40°58'N 5°30'W" />
	<input type="button" value="compute"
	       onclick="var t = GeodesicInverse(document.getElementById('inputa').value);
			document.getElementById('status').value = t.status;
			document.getElementById('p1').value = t.p1;
			document.getElementById('p2').value = t.p2;
			document.getElementById('s12').value = t.s12;" />
      </p>
      <p>
	&nbsp;lat1 lon1 azi1: <input type="text" "readonly" id="p1" size=60>
      </p>
      <p>
	&nbsp;lat2 lon2 azi2: <input type="text" "readonly" id="p2" size=60>
      </p>
      <p>
	&nbsp;s12: <input type="text" "readonly" id="s12" size=20>
	&nbsp;&nbsp; status: <input "readonly" id="status" size=30>
	&nbsp;&nbsp;
	<a href="geod-google-instructions.html"><b>INSTRUCTIONS HERE</b></a>
    </p>
  </div>
  </body>
</html>
